package com.fastCms.manager.frame.aspects;

import com.alibaba.fastjson.JSON;
import com.fastCms.business.frame.aspects.ExcludePathPatternsConstants;
import com.fastCms.business.frame.manage.CloudUserManage;
import com.fastCms.business.frame.response.ResponseUtil;
import com.fastCms.business.frame.utils.NetworkUtil;
import com.fastCms.business.frame.utils.TimesStatistics;
import com.fastCms.manager.frame.annotation.UserAccess;
import com.fastCms.manager.frame.annotation.UserAccessEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Description 访问权限控制
 * @param
 * @Return
 * @Date 2019/3/7 下午4:41 lsquan
 */
@Component
@Aspect
@Slf4j
public class TokenAccessAspect {

    public static final String TOKEN_FAIL = "token无效";
    public static final String TOKEN = "token";
    @Resource
    private CloudUserManage cloudUserManage;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // @Pointcut(value = "execution(* com.cloud.rsd.healthPro.web..*.controller..*(..))")
    @Pointcut(value = "execution(* com.fastCms..*.controller..*(..))")
    public void access() {

    }

    // @Pointcut(value = "execution(* com.cloud.rsd.healthPro.web..*.controller..*(..))")
    @Pointcut(value = "execution(* com.fastCms..*.controller..*(..))")
    public void accessReturn() {

    }

    /**
     * @Description 切面
     * @Param [pjp]
     * @Return java.lang.Object
     * @Date 2019/3/8 下午1:19 lsquan
     */
    @Around("access()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        TimesStatistics.begin();
        log.info("aspect拦截成功----");
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
        String Ip = NetworkUtil.getIpAddr(request);//获取被拦截的IP
        String queryString = request.getQueryString();
        String method = request.getMethod();
        log.info("请求Ip：{}", Ip);
        String uri = request.getRequestURI();//获取uri
        if (StringUtils.isNotBlank(uri)) {
            uri = uri.replaceAll("/+", "/");//访问路径中有可能有多个/
            log.info("请求uri：{}", uri);
        }

        Object[] args = pjp.getArgs();
        String params = "";
        //获取请求参数集合并进行遍历拼接
        if (args.length > 0) {
            if ("POST".equals(method)) {
                Object object = args[0];
                Map map = getKeyAndValue(object);
                params = JSON.toJSONString(map);
            } else if ("GET".equals(method)) {
                params = queryString;
            }
            log.info("请求param:{}", params);
        }
//        if (ArrayUtils.isNotEmpty(pjp.getArgs())) {
//            log.info("请求param:{}", JSON.toJSONString(pjp.getArgs()));
//        }
        //String test.ftl = req.getParameter("test.ftl");


        //匹配直接放行类
        if (Arrays.asList(ExcludePathPatternsConstants.RELEASE_CLASS).contains(pjp.getTarget().getClass().getName())) {//无需验证放行的方法f
            return pjp.proceed();
        }

        //匹配直接放行方法
        if (Arrays.asList(ExcludePathPatternsConstants.RELEASE_METHOD).contains(uri)) {//无需验证放行的方法
            return pjp.proceed();
        }


        //匹配类上注解
        UserAccess userClassAnnotation = pjp.getTarget().getClass().getAnnotation(UserAccess.class);
        if (userClassAnnotation != null) {
            //获取注解里的值
            if (userClassAnnotation.type() == UserAccessEnum.OPEN) {
                return pjp.proceed();
            }
        }

        //匹配方法上注解
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();//获取接口方法
        Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());//获取具体实现方法
        UserAccess userAccess = realMethod.getAnnotation(UserAccess.class);
        if (userAccess != null) {
            //获取注解里的值
            if (userAccess.type() == UserAccessEnum.OPEN) {
                return pjp.proceed();
            }
        }
        String token = getAccessTokenFromRequest(request);

        if (StringUtils.isBlank(token)) {
            log.error("[TOKEN_ACCESS]---fail---,reason:token为空");
            return ResponseUtil.tokenFail(TOKEN_FAIL);
        }

        HashMap<String, Object> map = CloudUserManage.getUserByToken(token);
        if (map == null) {
            log.error("[TOKEN_ACCESS]---fail---,reason:根据token查无用户;token:" + token);
            return ResponseUtil.tokenFail(TOKEN_FAIL);
        }
        //存入 ThreadLocal
        cloudUserManage.setUserToken(token);
        stringRedisTemplate.expire(token, 2, TimeUnit.HOURS);//设置token 有效期为2小时(每次更新token过期时间)
        return pjp.proceed();

    }

    /**
     * @Description 返回信息输出
     * @Param [jp, rvt]
     * @Return void
     * @Date 2019/3/9 下午4:54 lsquan
     */
    @AfterReturning(returning = "rvt", pointcut = "accessReturn()")
    public void accessReturn(JoinPoint jp, Object rvt) {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
        log.info("返回参数:{}", JSON.toJSONString(rvt));
        Long times = TimesStatistics.end();
        if (times >= 1000) {//超过1秒
            log.warn("耗时:" + times + ";URL:" + request.getRequestURL().toString() + ";HTTP_METHOD:" + request.getMethod());
        } else {
            //开发时打开
            log.info("耗时:" + times + ";URL:" + request.getRequestURL().toString() + ";HTTP_METHOD:" + request.getMethod());
        }
    }


    /**
     * @Description 从请求获取token
     * @Param [request]
     * @Return java.lang.String
     * @Date 2019/3/8 上午10:29 lsquan
     */
    protected String getAccessTokenFromRequest(HttpServletRequest request) {
        String accessToken = request.getHeader(TOKEN);
        if (StringUtils.isEmpty(accessToken)) {
            accessToken = request.getParameter(TOKEN);
        }
        return accessToken;
    }

    public static Map<String, Object> getKeyAndValue(Object obj) {
        Map<String, Object> map = new HashMap<>();
        // 得到类对象
        Class userCla = (Class) obj.getClass();
        /* 得到类中的所有属性集合 */
        Field[] fs = userCla.getDeclaredFields();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            f.setAccessible(true); // 设置些属性是可以访问的
            Object val = new Object();
            try {
                val = f.get(obj);
                // 得到此属性的值
                map.put(f.getName(), val);// 设置键值
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        return map;
    }
}


